﻿module HtmlDocument

open Dom
open System.Windows
open DocumentHost
open UriParsing
open JsEngine
open JsTypes
open ScriptingExposedAttribute
open StyledElements
open Uris


(* 
    Used by Window, HtmlDocument and HtmlElement
    Specific for HTML nodes because handlers rely on JavaScript engine
 *)
type EventTracker() =
    let mutable _handlers : Map<string, JsValue list> = Map.empty
    member this.addEventListener(name:string, handler:JsValue) =
        let addPair m (x, y) =
            match Map.tryFind x m with
            | Some l -> Map.add x (y::l) m
            | None -> Map.add x [y] m
        _handlers <- addPair _handlers (name,handler)

    member this.invokeListeners (event:Event) (jsengine : JsEngine) = 
        for handler in defaultArg (_handlers.TryFind(event.``type``)) [] do
            do jsengine.invokeValue(handler) |> ignore


and JsEngineFactory = Document -> IWindow -> JsEngine

type HTMLDocument(uri:HttpUrl, 
                    loader:IResourceLoader, 
                    domChangeCallback:DomChangedCallback,
                    elementFactory:ElementFactory, 
                    engineFactory:JsEngineFactory,
                    windowFactory:Document->IWindow) as this =
    inherit Document(uri, elementFactory, domChangeCallback)
    
    let _loader = loader
    let _window =  windowFactory this
    let _jsEngine = engineFactory this _window
    let _eventTracker = EventTracker() 
    
    let mutable _focusables = System.Collections.Generic.List<Element>()
    let mutable _focusElem : Element option = None

    member this.loader = _loader
    member this.defaultView = _window
    member this.jsEngine = _jsEngine
    member this.focusElement with get() = _focusElem  
    member this.setFocus(elem) = _focusElem <- Some elem

    member this.resolveUrl (url:string) =
        parseUrl url (Some(this.uri))

    [<ScriptingExposed>]
    member public this.getElementById(id:string) =
        let rec getElemById (elem:Element)  =
            if elem.attributes.TryFind "id" = Some id
            then Some elem
            else List.tryPick getElemById elem.childElements
        getElemById this.childElements.[0]

    override this.dispatchEvent (event:Event) =
        _eventTracker.invokeListeners event this.jsEngine
        // no default actions for document

    [<ScriptingExposed>]
    member public this.addEventListener(name:string, handler:JsValue) =
        _eventTracker.addEventListener(name, handler)

    // callec for everey node added to the doc
    override this.onAdd(node:Node) =        
        match node with
        | :? Text -> ()
        | :? Element as elem -> 
            if elem.isFocusable
                then do 
                    _focusables.Add(elem)
                    if this.focusElement = None 
                    then do _focusElem <- Some elem

    override this.dispatchKeyboardEvent eventName character =
        match this.focusElement with
            | None -> ()
            | Some elem ->
                do elem.dispatchEvent(KeyboardEvent(eventName, character))

    override this.createTextNode(text:string) =
        new StyledText(this, text) :> Text
